/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.chain2.web.servlet;

import java.util.Map;
import java.util.Objects;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.chain2.web.WebContextBase;

/**
 * <p>Concrete implementation of {@link org.apache.commons.chain2.web.WebContext} suitable for use in
 * Servlets and JSP pages.  The abstract methods are mapped to the appropriate
 * collections of the underlying servlet context, request, and response
 * instances that are passed to the constructor (or the initialize method).</p>
 *
 * @version $Id$
 */
public class ServletWebContextBase extends WebContextBase
        implements ServletWebContext<String, Object> {

    private static final long serialVersionUID = 20120724L;

    // ------------------------------------------------------------ Constructors
    /**
     * <p>Construct an uninitialized {@link ServletWebContextBase} instance.</p>
     */
    public ServletWebContextBase() {
        super();
    }
    /**
     * <p>Construct a {@link ServletWebContextBase} instance that is initialized
     * with the specified Servlet API objects.</p>
     *
     * @param context The <code>ServletContext</code> for this web application
     * @param request The <code>HttpServletRequest</code> for this request
     * @param response The <code>HttpServletResponse</code> for this request
     */
    public ServletWebContextBase(ServletContext context,
                                 HttpServletRequest request,
                                 HttpServletResponse response) {
        initialize(context, request, response);
    }

    // ------------------------------------------------------ Instance Variables

    /**
     * <p>The lazily instantiated <code>Map</code> of application scope
     * attributes.</p>
     */
    private ServletApplicationScopeMap applicationScope = null;

    /**
     * <p>The <code>ServletContext</code> for this web application.</p>
     */
    private transient ServletContext context = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of header name-value
     * combinations (immutable).</p>
     */
    private ServletHeaderMap header = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of header name-values
     * combinations (immutable).</p>
     */
    private ServletHeaderValuesMap headerValues = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of context initialization
     * parameters.</p>
     */
    private ServletInitParamMap initParam = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of cookies.</p>
     */
    private ServletCookieMap cookieValues = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request
     * parameter name-value.</p>
     */
    private ServletParamMap param = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request
     * parameter name-values.</p>
     */
    private ServletParamValuesMap paramValues = null;

    /**
     * <p>The <code>HttpServletRequest</code> for this request.</p>
     */
    private transient HttpServletRequest request = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of request scope
     * attributes.</p>
     */
    private ServletRequestScopeMap requestScope = null;

    /**
     * <p>The <code>HttpServletResponse</code> for this request.</p>
     */
    private transient HttpServletResponse response = null;

    /**
     * <p>The lazily instantiated <code>Map</code> of session scope
     * attributes.</p>
     */
    private ServletSessionScopeMap sessionScope = null;

    // ---------------------------------------------------------- Public Methods
    /** @see org.apache.commons.chain2.web.servlet.ServletWebContext#getContext() */
    @Override public ServletContext getContext() {
        return (this.context);
    }
    /** @see org.apache.commons.chain2.web.servlet.ServletWebContext#getRequest() */
    @Override public HttpServletRequest getRequest() {
        return (this.request);
    }
    /** @see org.apache.commons.chain2.web.servlet.ServletWebContext#getResponse() */
    @Override public HttpServletResponse getResponse() {
        return (this.response);
    }
    /** @see org.apache.commons.chain2.web.servlet.ServletWebContext#initialize(javax.servlet.ServletContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */
    @Override public void initialize(ServletContext context,
                           HttpServletRequest request,
                           HttpServletResponse response) {
        // Save the specified Servlet API object references
        this.context = context;
        this.request = request;
        this.response = response;

        // Perform other setup as needed
    }
    /** @see org.apache.commons.chain2.web.servlet.ServletWebContext#release() */
    @Override public void release() {
        // Release references to allocated collections
        applicationScope = null;
        header = null;
        headerValues = null;
        initParam = null;
        param = null;
        paramValues = null;
        cookieValues = null;
        requestScope = null;
        sessionScope = null;

        // Release references to Servlet API objects
        context = null;
        request = null;
        response = null;
    }

    // ------------------------------------------------------ WebContextBase Methods
    /** @see org.apache.commons.chain2.web.WebContext#getApplicationScope() */
    @Override public Map<String, Object> getApplicationScope() {
        if ((applicationScope == null) && (context != null)) {
            applicationScope = new ServletApplicationScopeMap(context);
        }
        return (applicationScope);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getHeader() */
    @Override public Map<String, String> getHeader() {
        if ((header == null) && (request != null)) {
            header = new ServletHeaderMap(request);
        }
        return (header);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getHeaderValues() */
    @Override public Map<String, String[]> getHeaderValues() {
        if ((headerValues == null) && (request != null)) {
            headerValues = new ServletHeaderValuesMap(request);
        }
        return (headerValues);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getInitParam() */
    @Override public Map<String, String> getInitParam() {
        if ((initParam == null) && (context != null)) {
            initParam = new ServletInitParamMap(context);
        }
        return (initParam);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getParam() */
    @Override public Map<String, String> getParam() {
        if ((param == null) && (request != null)) {
            param = new ServletParamMap(request);
        }
        return (param);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getParamValues() */
    @Override public Map<String, String[]> getParamValues() {
        if ((paramValues == null) && (request != null)) {
            paramValues = new ServletParamValuesMap(request);
        }
        return (paramValues);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getCookies() */
    @Override public Map<String, Cookie> getCookies() {
        if ((cookieValues == null) && (request != null)) {
            cookieValues = new ServletCookieMap(request);
        }
        return (cookieValues);
    }
    /** @see org.apache.commons.chain2.web.WebContext#getRequestScope() */
    @Override public Map<String, Object> getRequestScope() {
        if ((requestScope == null) && (request != null)) {
            requestScope = new ServletRequestScopeMap(request);
        }
        return (requestScope);
    }

    /** @see org.apache.commons.chain2.web.WebContext#getSessionScope() */
    @Override public Map<String, Object> getSessionScope() {
        if ((sessionScope == null) && (request != null)) {
            sessionScope = new ServletSessionScopeMap(request);
        }
        return (sessionScope);
    }

    /**
     * @see java.util.concurrent.ConcurrentHashMap#hashCode()
     */
    @Override
    public int hashCode() {
        return Objects.hash(
                getApplicationScope(),
                getHeader(),
                getHeaderValues(),
                getInitParam(),
                getParam(),
                getParamValues(),
                getCookies(),
                getRequestScope(),
                getSessionScope()) * 31 + super.hashCode();
    }

    /**
     * @see org.apache.commons.chain2.impl.ContextBase#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
        if (o != this) {
            if (!ServletWebContextBase.class.isInstance(o)) {
                return false;
            }

            ServletWebContextBase target = (ServletWebContextBase) o;
            return super.equals(target)
                    && Objects.equals(getApplicationScope(), target.getApplicationScope())
                    && Objects.equals(getHeader(), target.getHeader())
                    && Objects.equals(getHeaderValues(), target.getHeaderValues())
                    && Objects.equals(getInitParam(), target.getInitParam())
                    && Objects.equals(getParam(), target.getParam())
                    && Objects.equals(getParamValues(), target.getParamValues())
                    && Objects.equals(getCookies(), target.getCookies())
                    && Objects.equals(getRequestScope(), target.getRequestScope())
                    && Objects.equals(getSessionScope(), target.getSessionScope());
        }
        return true;
    }
}
